Numero complessi builtin

L'esempio più semplice in Python di istanza di una classe in Python è un oggetto che rappresenta un numero complesso.


In [1]:
a = 2 + 3j
print(a, type(a))


(2+3j) <class 'complex'>

Definizione di un nuovo ADT: NumeroComplesso

Vediamo ora come aggiungere un nuovo tipo che sia una nostra implementazione di un numero complesso. Una possibile implementazione di base è la seguente. Si noti che qualsiasi oggetto in Python eredità dall'oggetto base object.

ESERCIZIO 1: Completare il metodo somma nel codice seguente.


In [ ]:
class NumeroComplesso(object):
    def __init__(self, real, imag):
        """Metodo costruttore, chiamato quando viene 
           inizializzato un nuovo oggetto"""
        self.a = real
        self.b = imag
        
    def somma(self, c):
        """Somma al numero corrente il numero complesso c"""
        # DA COMPLETARE
        pass
        
    def __str__(self):
        """Ritorna una stringa che rappresenta il numero"""
        return str(self.a) + ' + ' + str(self.b) +'i'

In [ ]:
type(NumeroComplesso)

In [ ]:
a = NumeroComplesso(2,3)
b = NumeroComplesso(1,-2)
print(a)
a.somma(b)
print(a)

Danger ZONE !!!


In [ ]:
a.a = 0
a.somma(b)
print(a)

Inheritance e Operator Overloading

Supponiamo ora di definire un nuovo tipo chiamato NCO che rappresenta un numero complesso, ma su cui vogliamo specificare l'overloading di due operazione:

  1. La somma di due numeri complessi
  2. L'operatore di confronto logico di uguaglianza tra due numeri complessi

Per specificare che la classe NCO estende la classe base NumeroComplesso basta dichiararla come:

class NCO(NumeroComplesso):

Si notiche si è racchiuso tra parentesi il nomde del tipo da cui si ereditano attributi e metodi.

ESERCIZIO 2: Si completi il metodo __add__ nella classe NCO in modo che restituisca un nuovo oggetto che, dati due numeri a e b, rappresenti il numero complesso c = a + b.


In [ ]:
class NCO(NumeroComplesso):
    # EREDITA DI I METODI E ATTRIBUTI DELLA CLASSE "NUMERO COMPLESSO"
    def __add__(self, c):
        """Esempio di OPERATOR OVERLOADING: addizione"""
        # DA COMPLETARE
        pass
    
    def __eq__(self, c):
        """Esempio di OPERATOR OVERLOADING: confronto"""
        return self.a == c.a and self.b == c.b

In [ ]:
c = NCO(1,2)
print(c)

In [ ]:
type(c.somma)

In [ ]:
type(c.a)

In [ ]:
a == c

In [ ]:
d = NCO(1,2)
print(c, id(c))
print(d, id(d))
print(d == c)

Classes vs. Closures


In [ ]:
# Definizione di una classe che implementa un "adder"
class Adder(object):
    def __init__(self, n=0):
        self.n = n   # Stato mutabile della classe (DANGER ZONE!!!)
    def __call__(self, m):
        return self.n + m
    
add5_istanza = Adder(5)
print(add5_istanza(1), add5_istanza(7))

In [ ]:
# Definizione di una closure che implementa un "adder"
def make_adder(n=0):
    def adder(m):
        return n + m
    return adder
add5_function = make_adder(5)
print(add5_function(1), add5_function(7))

In [ ]:
add5_istanza.n = 1
print(add5_istanza(1), add5_istanza(7))
print(add5_function(1), add5_function(7))

NOTA: Con gli oggetti per poter capire l'esecuzione del codice bisogna capire qual'è stata la storia di esecuzione precedente...


In [ ]:
# Definizione di una classe che implementa un "counter"
class Counter(object):
    def __init__(self, n=0):
        self.n = n
    def __call__(self):
        self.n += 1
        return self.n
    
counter_istanza = Counter(5)
print(counter_istanza(), counter_istanza())

In [ ]:
# Definizione di una closure che implementa un "counter"
def make_counter(n=0):
    def state():
        c = n
        while True:
            c += 1
            yield c
        
    def counter():
        return next(f)
        
    f = state()
    
    return counter

counter_function = make_counter(5)
print(counter_function(), counter_function())

In [ ]:
# faccio una modifica apparentemente "innocua"
counter_istanza.n = 'ciao'

In [ ]:
print(counter_function(), counter_function())

In [ ]:
# ... dopo un po` riutilizzo il counter_istanza
print(counter_istanza())

In [ ]: